package model.market;

import java.util.*;

import model.trader.MarketMaker;

//AMLOW: comments..
//AMLOW: maybe cut market makeers in a split somehow?
public class AuctionEngine implements MarketEngine{

	public PeriodResults executeOrders(int period,AssetTradeRequests audit,
											double marketPrice) {
		
		
		PriceAndSplits priceAndSplits = discoverPrice(audit, marketPrice);

		marketPrice=priceAndSplits.marketPrice;
		double buySplitPrice = priceAndSplits.buySplitPrice;
		double sellSplitPrice = priceAndSplits.sellSplitPrice;
		
		
		
		if(marketPrice>buySplitPrice||marketPrice<sellSplitPrice){
			throw new RuntimeException();
		}
		
		//AM: no need.. drawOrderBook(audit,priceAndSplits);
		
		//AM: ok, now we have price, lets find balance..
		int totalBuy = 0;
		for(Trade buy:audit.getDailyBuys()){
			
			if(buy.isMarketOrder()||buy.getLimitPrice()>=buySplitPrice){
				totalBuy+=buy.getQuantity();
			}else{
				break;
			}
		}
		
		int totalSell= 0;
		for(Trade sellTrade:audit.getDailySells()){
			
			if(sellTrade.isMarketOrder()||sellTrade.getLimitPrice()<=sellSplitPrice){
				totalSell+=sellTrade.getQuantity();
			}else{
				break;
			}
		}
		
		List<Execution> executions = new ArrayList<Execution>();
		
		if(totalSell>totalBuy){
			
			//We need to split the last sells.
			int remainder = totalBuy;
			List<Trade> splitTrades =new ArrayList<Trade>();
			int desiredOnSplit = 0;
			for(Trade sellTrade:audit.getDailySells()){
				if(sellTrade.isMarketOrder()||sellTrade.getLimitPrice()<sellSplitPrice){
					executions.add(new Execution(period,null,sellTrade,sellTrade.getQuantity()));
					remainder-=sellTrade.getQuantity();
				}else{
					//now we're down to the split..
					if(sellTrade.getLimitPrice()==sellSplitPrice){
						splitTrades.add(sellTrade);
						desiredOnSplit+=sellTrade.getQuantity();
					}else{
						break;
					}
				}
			}
			executions.addAll(fillSplits(period,remainder, splitTrades,desiredOnSplit,false));
			
			for(Trade buyTrade:audit.getDailyBuys()){
				if(buyTrade.isMarketOrder()||buyTrade.getLimitPrice()>=marketPrice){
					executions.add(new Execution(period,buyTrade,null,buyTrade.getQuantity()));
					
				}else{
					break;
				}
			}
			
			
			
		}else if(totalBuy>totalSell){
			//We need to split the last buys.
			int remainder = totalSell;
			List<Trade> splitTrades =new ArrayList<Trade>();
			int desiredOnSplit = 0;
			for(Trade buy:audit.getDailyBuys()){
				if(buy.isMarketOrder()||buy.getLimitPrice()>buySplitPrice){
					executions.add(new Execution(period,buy,null,buy.getQuantity()));
					remainder-=buy.getQuantity();
				}else{
					//now we're down to the split..
					if(buy.getLimitPrice()==buySplitPrice){
						splitTrades.add(buy);
						desiredOnSplit+=buy.getQuantity();
					}else{
						break;
					}
				}
			}
			
			executions.addAll(fillSplits(period, remainder, splitTrades,desiredOnSplit,true));
			
			for(Trade sell:audit.getDailySells()){
				if(sell.isMarketOrder()||sell.getLimitPrice()<=marketPrice){
					executions.add(new Execution(period,null,sell,sell.getQuantity()));
					
				}else{
					break;
				}
			}
			
		}else{
			//perfect.. lets just match em up..
			for(Trade buyTrade:audit.getDailyBuys()){
				if(buyTrade.isMarketOrder()||buyTrade.getLimitPrice()>=buySplitPrice){
					executions.add(new Execution(period,buyTrade,null,buyTrade.getQuantity()));
					
				}else{
					break;
				}
			}
			
			for(Trade sellTrade:audit.getDailySells()){
				if(sellTrade.isMarketOrder()||sellTrade.getLimitPrice()<=sellSplitPrice){
					executions.add(new Execution(period,null,sellTrade,sellTrade.getQuantity()));
					
				}else{
					break;
				}
			}
			
		}
		
		//set execution prices..
		for(Execution execution:executions){
			execution.setPrice(marketPrice);
			
		}
		
		int boughtExec=0;
		int soldExec=0;
		List<Trade> executedTrades= new ArrayList<Trade>();
		for(Execution exec: executions){
			if(exec.getBuy()!=null){
				boughtExec+=exec.getQuantity();
				executedTrades.add(exec.getBuy());
			}
			if(exec.getSell()!=null){
				soldExec+=exec.getQuantity();
				executedTrades.add(exec.getSell());
			}
		}
		
		if(soldExec!=boughtExec){
			drawOrderBook(audit, priceAndSplits);
			throw new RuntimeException("matching mistake.. Sold "+soldExec+" bought "+boughtExec);
		}
		
		PeriodResults results = new PeriodResults();
		results.setMarketPrice(marketPrice);
		results.setExecutions(executedTrades);
		return results;
	}

	private void drawOrderBook(AssetTradeRequests audit,
			PriceAndSplits priceAndSplits) {
		
		double start = priceAndSplits.marketPrice*1.08;
		
		System.out.println("Market price: "+priceAndSplits.marketPrice+" buy split: "+priceAndSplits.buySplitPrice+" sell split: "+priceAndSplits.sellSplitPrice);
		while(start>priceAndSplits.marketPrice*.92){
			
			int buyNumber=0;
			for(Trade buy:audit.getDailyBuys()){
				if(buy.isMarketOrder()||buy.getLimitPrice()>=start){
					buyNumber+=buy.getQuantity();
				}
			}
			
			int sellNumber=0;
			for(Trade sell:audit.getDailySells()){
				if(sell.isMarketOrder()||sell.getLimitPrice()<=start){
					sellNumber+=sell.getQuantity();
				}
			}
			
			System.out.println(""+(start)+"	"+buyNumber+"	"+sellNumber);
			
			start-=priceAndSplits.marketPrice*.005;
		}
		
	}

	public List<Execution> fillSplits(int period, int splitAmount, List<Trade> splitTrades, int desiredOnSplit, boolean buying) {
		int originalSplit = splitAmount;
		
		
		int originalDesire = desiredOnSplit;
		List<Execution> executions=new ArrayList<Execution>();
		
		for(int count = 0;count<splitTrades.size();count++){
			Trade split = splitTrades.get(count);
			if(splitAmount<0||desiredOnSplit<=0||split.getQuantity()<0){
				throw new RuntimeException("negative split? "+splitAmount+" "+desiredOnSplit+" "+splitTrades);
			}
			//AM: needed to break this because we were popping max integer!!
			double fractionOfSplit = ((double)splitAmount)/desiredOnSplit;
			int trade= (int)(split.getQuantity()*fractionOfSplit);
			
			//AM: this means that we  balance trades exactly and capture rounding errors.
			if(count==splitTrades.size()-1){
				if(splitAmount>split.getQuantity()){
					System.out.println("Trader unhappy: "+splitAmount+"  "+split.getQuantity()+
							"  origdesire "+originalDesire+" origsplit "+originalSplit);
					
					for(int i=0;i<splitTrades.size();i++){
							System.out.println("Split trade: "+splitTrades.get(i)+"  "+splitTrades.get(i).getExecutedQuantity());
					}
					throw new RuntimeException("Failure to split trades correctly");
				}
				trade=splitAmount;
			}
			if(trade!=0){
				if(buying){
					executions.add(new Execution(period,split,null,trade));
				}else{
					executions.add(new Execution(period,null,split,trade));
				}
			}
			desiredOnSplit-=split.getQuantity();
			splitAmount-=trade;
		}
		return executions;
	}
	
	public class PriceAndSplits{
		public double marketPrice;
		public double buySplitPrice;
		public double sellSplitPrice;
	}

	public PriceAndSplits discoverPrice(AssetTradeRequests audit, double marketPrice) {
		PriceAndSplits ret = new PriceAndSplits();
		ret.buySplitPrice=ret.sellSplitPrice=marketPrice;
		
		Iterator<Trade> sells = audit.getDailySells().iterator();
		Trade sell=null;
		if(sells.hasNext()){
			 sell = sells.next();
			 if(!sell.isMarketOrder()){
				 ret.sellSplitPrice=sell.getLimitPrice();
			 }
		}else{
			//Blow up??
			throw new MarketException("Nobody is selling!!");
		}
		int quantityToSell = sell.getQuantity();
		
		outer:for (Iterator<Trade> buys = audit.getDailyBuys().iterator(); buys.hasNext();) {
			Trade buy = (Trade) buys.next();
			if(!buy.isMarketOrder()){
				ret.buySplitPrice=buy.getLimitPrice();
			}
			int quantityToBuy = buy.getQuantity();
			while(quantityToBuy>0){
				if(quantityToSell==0){
					//need the next sell order..
					if(sells.hasNext()){
						 sell = sells.next();
						 quantityToSell = sell.getQuantity();
						 if(!sell.isMarketOrder()){
							 ret.sellSplitPrice=sell.getLimitPrice();
						 }
					}else if((buy.isMarketOrder())){
						//Blow up??
						throw new MarketException("Nobody is selling!!");
					}else{
						//we've reconciled all we can..
						break outer;
					}
				}
				
				if(buy.isMarketOrder()){
					if(!sell.isMarketOrder()){
						//sell has set price..
						marketPrice=Math.max(sell.getLimitPrice(),marketPrice);
						ret.sellSplitPrice=sell.getLimitPrice();
					}else{
						//both aggressive
						//we have a simple deal, market price need not change.
					}
				}else{
					//Passive buy..
					if(sell.isMarketOrder()){
						//use the buy price..
						marketPrice=Math.min(buy.getLimitPrice(),marketPrice);
						ret.buySplitPrice=buy.getLimitPrice();
					}else{
						//AM: overlap on limit orders?
						//No deal!!
						if(sell.getLimitPrice()>buy.getLimitPrice()){
							break outer;
						}
						ret.buySplitPrice=buy.getLimitPrice();
						ret.sellSplitPrice=sell.getLimitPrice();
						
						//AM: choose closest to current market price.
						if(sell.getLimitPrice()<marketPrice&&
								buy.getLimitPrice()>marketPrice){
							//No problem, keep same market price.
						}else{
							//We know they overlap, but not sure which is closest to market.
							double sellSpread = Math.abs(marketPrice-sell.getLimitPrice());
							double buySpread = Math.abs(marketPrice-buy.getLimitPrice());
							if(sellSpread<=buySpread){
								marketPrice=sell.getLimitPrice();
							}else{
								marketPrice = buy.getLimitPrice();
							}
						}
					}
					
				}
				
				int executionNumber =quantityToBuy;
				if(quantityToBuy>quantityToSell){
					executionNumber= quantityToSell;
				}
				quantityToBuy-=executionNumber;
				quantityToSell-=executionNumber;
				
				
			}
		}
		//were there any sells left??
		if(sell!=null){
			if(quantityToSell==0){
				if(sells.hasNext()){
					sell=sells.next();
					if(sell!=null&&sell.isMarketOrder()){
						throw new MarketException("Not enough buyers!");
					}
				}
			}else if(sell.isMarketOrder()){
				throw new MarketException("Not enough buyers!");
			}
		}
		
		ret.marketPrice=marketPrice;
		
		if(ret.marketPrice>ret.buySplitPrice){
			ret.buySplitPrice=ret.marketPrice;
		}
		if(ret.marketPrice<ret.sellSplitPrice){
			ret.sellSplitPrice=ret.marketPrice;
		}
		return ret;
	}

}
